package com.liuya.db.orm.impl;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;

import com.liuya.common.Util;
import com.liuya.db.DBLevelException;
import com.liuya.db.ObjectNewer;
import com.liuya.db.util.DBUtil;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import com.liuya.db.orm.Column;
import com.liuya.db.orm.ComplexTable;
import com.liuya.db.orm.ComplexTableDBHelper;
import com.liuya.db.orm.ComplexTableSelector;
import com.liuya.db.orm.ComplexTableValueCombiner;
import com.liuya.db.orm.InnerWhereElement;
import com.liuya.db.orm.MultiValueTableAdapter;
import com.liuya.db.orm.SelectCondition;
import com.liuya.db.orm.SelectConditionUtil;
import com.liuya.db.orm.SingleValueTableAdapter;
import com.liuya.db.orm.Table;

public class ComplexTableSelectorImpl implements ComplexTableSelector {
	private ComplexTable complexTable;

	private ObjectNewer mainTableObjectNewer;
	private SingleValueTableAdapter[] singleValueAdapters;
	private MultiValueTableAdapter[] multiValueAdapters;

	private TableSelectorImpl mainTableSelector;
	private TableSelectorImpl[] singleValueTableSelectors;
	private TableSelectorImpl[] multiValueTableSelectors;

	private SelectCondition byIdColumnsSelectCdtn;

	private static Log logger = LogFactory.getLog(ComplexTableSelectorImpl.class);

	public ComplexTable getComplexTable() {
		return complexTable;
	}

	public void setComplexTable(ComplexTable complexTable) {
		this.complexTable = complexTable;

		byIdColumnsSelectCdtn = SelectConditionUtil.simplyConnectColumns(complexTable.getMainTable().getIdColumns());

		Table mainTable = complexTable.getMainTable();
		mainTableSelector = new TableSelectorImpl();
		mainTableSelector.setTable(mainTable);
		innerSetMainTableObjectNewer();

		Table[] singleValueTables = complexTable.getSingleValueTables();
		singleValueTableSelectors = getTableSelectors(singleValueTables);
		innerSetSingleValueTableObjectNewer();

		Table[] multiValueTables = complexTable.getMultiValueTables();
		multiValueTableSelectors = getTableSelectors(multiValueTables);
		innerSetMultiValueTableObjectNewer();
	}

	public void setMainTableObjectNewer(ObjectNewer mainTableObjectNewer) {
		this.mainTableObjectNewer = mainTableObjectNewer;

		innerSetMainTableObjectNewer();
	}

	private void innerSetMainTableObjectNewer() {
		if (mainTableSelector != null) {
			mainTableSelector.setObjectNewer(mainTableObjectNewer);
		}
	}

	private void innerSetSingleValueTableObjectNewer() {
		if (Util.isEmpty(singleValueAdapters) || Util.isEmpty(singleValueTableSelectors))
			return;

		for (int i = 0; i < singleValueAdapters.length; i++) {
			singleValueTableSelectors[i].setObjectNewer(singleValueAdapters[i].getObjectNewer());
		}
	}

	private void innerSetMultiValueTableObjectNewer() {
		if (Util.isEmpty(multiValueAdapters) || Util.isEmpty(multiValueTableSelectors))
			return;

		for (int i = 0; i < multiValueAdapters.length; i++) {
			multiValueTableSelectors[i].setObjectNewer(multiValueAdapters[i].getObjectNewer());
		}
	}

	private TableSelectorImpl[] getTableSelectors(Table[] tables) {
		if (Util.isEmpty(tables))
			return null;

		TableSelectorImpl[] impls = new TableSelectorImpl[tables.length];
		for (int i = 0; i < tables.length; i++) {
			TableSelectorImpl impl = new TableSelectorImpl();
			impl.setTable(tables[i]);

			impls[i] = impl;
		}

		return impls;
	}

	public SingleValueTableAdapter[] getSingleValueAdapters() {
		return singleValueAdapters;
	}

	public void setSingleValueAdapters(SingleValueTableAdapter[] singleValueAdapters) {
		this.singleValueAdapters = singleValueAdapters;

		innerSetSingleValueTableObjectNewer();
	}

	public MultiValueTableAdapter[] getMultiValueAdapters() {
		return multiValueAdapters;
	}

	public void setMultiValueAdapters(MultiValueTableAdapter[] adapters) {
		this.multiValueAdapters = adapters;

		innerSetMultiValueTableObjectNewer();
	}

	public Object selectByIdColumns(Object o) throws DBLevelException {
		// load main table values
		Object selectValue = mainTableSelector.selectByIdColumns(o);
		if (selectValue == null)
			return null;

		// load single value table values
		if (!Util.isEmpty(singleValueTableSelectors)) {
			for (int i = 0; i < singleValueTableSelectors.length; i++) {
				Object temp = singleValueAdapters[i].extractEvenNoValueExist(o);
				Object tableSelectValue = singleValueTableSelectors[i].selectByIdColumns(temp);
				if (tableSelectValue != null) {
					singleValueAdapters[i].combine(selectValue, tableSelectValue);
				}
			}
		}

		// load multi value table values
		if (!Util.isEmpty(multiValueTableSelectors)) {
			for (int i = 0; i < multiValueTableSelectors.length; i++) {
				Object temp = multiValueAdapters[i].extractEvenNoValueExist(o);
				Collection tableSelectValue = multiValueTableSelectors[i].select(byIdColumnsSelectCdtn, temp);
				if (tableSelectValue.size() > 0) {
					multiValueAdapters[i].combine(selectValue, tableSelectValue.toArray());
				}
			}
		}

		return selectValue;
	}

	public Object selectByUniqueColumns(Object o) throws DBLevelException {
		// load main table values
		Object selectValue = mainTableSelector.selectByUniqueColumns(o);
		if (selectValue == null)
			return null;

		// load single value table values
		if (!Util.isEmpty(singleValueTableSelectors)) {
			for (int i = 0; i < singleValueTableSelectors.length; i++) {
				Object temp = singleValueAdapters[i].extractEvenNoValueExist(o);
				Object tableSelectValue = singleValueTableSelectors[i].selectByIdColumns(temp);
				if (tableSelectValue != null) {
					singleValueAdapters[i].combine(selectValue, tableSelectValue);
				}
			}
		}

		// load multi value table values
		if (!Util.isEmpty(multiValueTableSelectors)) {
			for (int i = 0; i < multiValueTableSelectors.length; i++) {
				Object temp = multiValueAdapters[i].extractEvenNoValueExist(o);
				Collection tableSelectValue = multiValueTableSelectors[i].select(byIdColumnsSelectCdtn, temp);
				if (tableSelectValue.size() > 0) {
					multiValueAdapters[i].combine(selectValue, tableSelectValue.toArray());
				}
			}
		}

		return selectValue;
	}

	public Collection select(SelectCondition cdtn, Object hint) throws DBLevelException {
		Map tempTableNameConnMap = null;
		ComplexTableDBHelper helper = new ComplexTableDBHelper();
		helper.setComplexTable(complexTable);
		try {
			helper.getConnections();
			helper.beginTransaction();

			// select main table
			Collection coll = mainTableSelector.select(helper.getMainTableConn(), cdtn, hint);

			if (coll.isEmpty()) {
				return coll;
			}

			ComplexTableValueCombiner combiner = new ComplexTableValueCombiner(complexTable);
			combiner.setMainTableValues(coll);
			combiner.setSingleValueTableAdapters(singleValueAdapters);
			combiner.setMultiValueTableAdapters(multiValueAdapters);

			// save idColumns as a temp table
			Column[] idColumns = complexTable.getMainTable().getIdColumns();
			// Collection pks=combiner.getGeneralPks();
			tempTableNameConnMap = new HashMap();
			createTempTables(helper.getSingleValueTableConns(), idColumns, coll, tempTableNameConnMap);
			createTempTables(helper.getMultiValueTableConns(), idColumns, coll, tempTableNameConnMap);

			// according to temp table, query singlevaluetables and
			// multivaluetables
			Collection[] readSingleValueTables = readSingleValueTables(helper.getSingleValueTableConns(), tempTableNameConnMap);
			Collection[] readMultiValueTables = readMultiValueTables(helper.getMultiValueTableConns(), tempTableNameConnMap);

			combiner.setSingleValueTableValues(readSingleValueTables);
			combiner.setMultiValueTableValues(readMultiValueTables);

			return combiner.combine();
		} catch (SQLException e) {
			logger.error("", e);
			throw new DBLevelException(e);
		} finally {
			// drop temp tables
			if (tempTableNameConnMap != null) {
				for (Iterator iter = tempTableNameConnMap.entrySet().iterator(); iter.hasNext();) {
					Entry entry = (Entry) iter.next();
					Connection conn = (Connection) entry.getKey();
					String tempTableName = (String) entry.getValue();

					try {
						DBUtil.exec(conn, "DROP TABLE " + tempTableName);
					} catch (SQLException e) {
						logger.error("", e);
					}
				}
			}

			helper.closeConnections();
		}
	}

	public Collection selectByPage(SelectCondition cdtn, Object hint, int startIndex, int pageSize) throws DBLevelException {
		Map tempTableNameConnMap = null;
		ComplexTableDBHelper helper = new ComplexTableDBHelper();
		helper.setComplexTable(complexTable);
		try {
			helper.getConnections();
			helper.beginTransaction();

			// query main table
			Collection coll = mainTableSelector.selectByPage(helper.getMainTableConn(), cdtn, hint, startIndex, pageSize);

			if (coll.isEmpty()) {
				return coll;
			}

			ComplexTableValueCombiner combiner = new ComplexTableValueCombiner(complexTable);
			combiner.setMainTableValues(coll);
			combiner.setSingleValueTableAdapters(singleValueAdapters);
			combiner.setMultiValueTableAdapters(multiValueAdapters);

			// save idColumns as a temp table
			Column[] idColumns = complexTable.getMainTable().getIdColumns();
			// Collection pks=combiner.getGeneralPks();
			tempTableNameConnMap = new HashMap();
			createTempTables(helper.getSingleValueTableConns(), idColumns, coll, tempTableNameConnMap);
			createTempTables(helper.getMultiValueTableConns(), idColumns, coll, tempTableNameConnMap);

			// according temp table, query singlevalue and multivalue tables
			Collection[] readSingleValueTables = readSingleValueTables(helper.getSingleValueTableConns(), tempTableNameConnMap);
			Collection[] readMultiValueTables = readMultiValueTables(helper.getMultiValueTableConns(), tempTableNameConnMap);

			combiner.setSingleValueTableValues(readSingleValueTables);
			combiner.setMultiValueTableValues(readMultiValueTables);

			return combiner.combine();
		} catch (SQLException e) {
			logger.error("", e);
			throw new DBLevelException(e);
		} finally {
			// drop temp tables
			if (tempTableNameConnMap != null) {
				for (Iterator iter = tempTableNameConnMap.entrySet().iterator(); iter.hasNext();) {
					Entry entry = (Entry) iter.next();
					Connection conn = (Connection) entry.getKey();
					String tempTableName = (String) entry.getValue();

					try {
						DBUtil.exec(conn, "DROP TABLE " + tempTableName);
					} catch (SQLException e) {
						logger.error("", e);
					}
				}
			}

			helper.closeConnections();
		}
	}

	private Collection[] readSingleValueTables(Connection[] conns, Map tempTableNameConnMap) {
		Collection[] readResult = null;

		Table[] tables = complexTable.getSingleValueTables();
		TableSelectorImpl[] selectors = singleValueTableSelectors;

		if (!Util.isEmpty(tables)) {
			readResult = new Collection[tables.length];
			for (int i = 0; i < tables.length; i++) {
				Connection conn = conns[i];
				String tempTableName = (String) tempTableNameConnMap.get(conn);

				InnerWhereElement whereEmt = new InnerWhereElement();
				whereEmt.setColumns(complexTable.getMainTable().getIdColumns());
				whereEmt.setInnerTableColumnNames(complexTable.getMainTable().getIdColumnNames());
				whereEmt.setTableName(tempTableName);

				SelectCondition cdtn = new SelectCondition();
				cdtn.setWhereElement(whereEmt);
				Collection coll = selectors[i].select(conn, cdtn, null);
				readResult[i] = coll;
			}
		}

		return readResult;
	}

	private Collection[] readMultiValueTables(Connection[] conns, Map tempTableNameConnMap) {
		Collection[] readResult = null;
		Table[] tables = complexTable.getMultiValueTables();
		TableSelectorImpl[] selectors = multiValueTableSelectors;

		if (!Util.isEmpty(tables)) {
			readResult = new Collection[tables.length];
			for (int i = 0; i < tables.length; i++) {
				Connection conn = conns[i];
				String tempTableName = (String) tempTableNameConnMap.get(conn);

				InnerWhereElement whereEmt = new InnerWhereElement();
				whereEmt.setColumns(complexTable.getMainTable().getIdColumns());
				whereEmt.setInnerTableColumnNames(complexTable.getMainTable().getIdColumnNames());
				whereEmt.setTableName(tempTableName);

				SelectCondition cdtn = new SelectCondition();
				cdtn.setWhereElement(whereEmt);
				Collection coll = selectors[i].select(conn, cdtn, null);
				readResult[i] = coll;
			}
		}

		return readResult;
	}

	private void createTempTables(Connection[] conns, Column[] idColumns, Collection tableValues, Map tempTableNameConnMap) {
		if (!Util.isEmpty(conns)) {
			for (int i = 0; i < conns.length; i++) {
				Connection conn = conns[i];
				if (tempTableNameConnMap.containsKey(conn)) {
					// already done
				} else {
					// create and insert value
					String table = createTempTable(conn, idColumns, tableValues);

					tempTableNameConnMap.put(conn, table);
				}
			}
		}
	}

	private String createTempTable(Connection conn, Column[] insertColumns, Collection tableValues) {
		String tempTableName = "Tmp_" + System.currentTimeMillis() + ((int) (Math.random() * 100));

		String[] columnNames = new String[insertColumns.length];
		String[] columnSqlTypes = new String[insertColumns.length];
		for (int i = 0; i < insertColumns.length; i++) {
			Column column = insertColumns[i];
			columnNames[i] = column.getName();
			columnSqlTypes[i] = column.getType().getSqlType();
		}
		String createSql = DBUtil.createTableSql(tempTableName, columnNames, columnSqlTypes);

		PreparedStatement pstmt = null;
		try {
			pstmt = conn.prepareStatement(createSql);
			pstmt.execute();
		} catch (SQLException e) {
			logger.error("", e);
			throw new DBLevelException(e);
		} finally {
			DBUtil.close(pstmt);
		}

		Table table = new Table();
		table.setColumnNames(columnNames);
		table.setColumns(insertColumns);
		table.setExceptIdColumnNames(columnNames);
		table.setExceptIdColumns(insertColumns);
		table.setName(tempTableName);

		TableSaverImpl saver = new TableSaverImpl();
		saver.setTable(table);
		saver.batchSave(conn, tableValues);

		return tempTableName;
	}

	public int selectCount() throws DBLevelException {
		return mainTableSelector.selectCount(null, null);
	}

	public int selectCount(SelectCondition cdtn, Object hint) throws DBLevelException {
		return mainTableSelector.selectCount(cdtn, hint);
	}
}
